package fr.teuteuf.runstickmanrun.screen;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.Controllers;
import com.badlogic.gdx.controllers.mappings.Ouya;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;

import fr.teuteuf.runstickmanrun.AbstractKeyMap;
import fr.teuteuf.runstickmanrun.Constants;
import fr.teuteuf.runstickmanrun.IRenderable;
import fr.teuteuf.runstickmanrun.IUpdatable;
import fr.teuteuf.runstickmanrun.RunStickmanRun;
import fr.teuteuf.runstickmanrun.entity.AbstractEntity;
import fr.teuteuf.runstickmanrun.entity.BigLeftMonster;
import fr.teuteuf.runstickmanrun.entity.Player;
import fr.teuteuf.runstickmanrun.entity.comparator.ComparatorPlayerByDistance;
import fr.teuteuf.runstickmanrun.map.tile.IDamagingTile;
import fr.teuteuf.runstickmanrun.map.tile.SolidBoxTile;
import fr.teuteuf.runstickmanrun.map.tile.Tile;

public class MultiPlayerScreen extends AbstractGameScreen {
	
	private final float BONUS_REGEN = 1f;
	private final float MOVING_SMOOTH = 2000f;
	private final float ZOOMING_SMOOTH = 1f;
	
	private String debugMsg = "";
	private Box2DDebugRenderer debugRenderer;
	
	private OrthographicCamera gameCamera;
	private SpriteBatch gameBatch;
	private OrthographicCamera interfaceCamera;
	private SpriteBatch interfaceBatch;
	private Texture background;
	private int backgroundPosition;
	
	private Array<Player> players;
	private Array<Player> deadPlayers;
	private Array<Player> sortedPlayers;
	private ComparatorPlayerByDistance comparatorPlayerByDistance;
	
	private AbstractKeyMap keyMap;
	
	public MultiPlayerScreen(RunStickmanRun runStickmanRun) {
		this.keyMap = runStickmanRun.getKeyMap();
		this.runStickmanRun = runStickmanRun;
	}

	@Override
	public void create() {
		super.create();
		Tile.loadTexture();
		Player.loadTexture();
		BigLeftMonster.loadTexture();
		background = new Texture(Gdx.files.internal("backgrounds/rocks.png"));
		
		reinit();
	}

	@Override
	public void reinit() {
		super.reinit();
		
		gameCamera = new OrthographicCamera();
		gameCamera.setToOrtho(false, Constants.WIDTH_RESOLUTION, Constants.HEIGHT_RESOLUTION);
		gameBatch = new SpriteBatch();
		interfaceCamera = new OrthographicCamera();
		interfaceCamera.setToOrtho(false, Constants.WIDTH_RESOLUTION, Constants.HEIGHT_RESOLUTION);
		interfaceBatch = new SpriteBatch();
		backgroundPosition = 0;
		
		players = new Array<Player>();
		deadPlayers = new Array<Player>();
		if(runStickmanRun.isOnOuya()) {
			for(int i = 0; i < Controllers.getControllers().size; i++) {
				players.add(new Player(world, (7 + players.size)  * Tile.TILE_SIZE, Tile.TILE_SIZE * 1, 0, 0, i, "P"+(i+1)));
			}
		} else {
			players.add(new Player(world, (7 + players.size)  * Tile.TILE_SIZE, Tile.TILE_SIZE * 1, 0, 0, 0, "P1"));
			players.add(new Player(world, (7 + players.size)  * Tile.TILE_SIZE, Tile.TILE_SIZE * 1, 0, 0, 1, "P2"));
		}
		sortedPlayers = new Array<Player>(players);
		updatables.addAll(players);
		
		comparatorPlayerByDistance = new ComparatorPlayerByDistance();
		
		if(Constants.IS_DEBUG_MODE) {
			debugRenderer = new Box2DDebugRenderer();
		}
	}

	@Override
	public void update(float elapsedTime) {
		super.update(elapsedTime);
		world.step(1/60f, 6, 2);
		
		float xFirst = getFirstPlayer().getPosition().x;
		float xLast = getLastPlayer().getPosition().x;
		
		float zoom = (Math.abs(xFirst - xLast) * Constants.BOX_TO_WORLD + Constants.WIDTH_RESOLUTION / 2) / Constants.WIDTH_RESOLUTION;
		if(zoom < gameCamera.zoom && gameCamera.zoom - ZOOMING_SMOOTH * elapsedTime > zoom) {
			gameCamera.zoom = gameCamera.zoom - ZOOMING_SMOOTH * elapsedTime;
		} else if(zoom > gameCamera.zoom && gameCamera.zoom + ZOOMING_SMOOTH * elapsedTime < zoom) {
			gameCamera.zoom = gameCamera.zoom + ZOOMING_SMOOTH * elapsedTime; 
		} else {
			gameCamera.zoom = zoom;
		}
		
		if(gameCamera.zoom < 1) {
			gameCamera.zoom = 1f;
		}
		
		//Update game camera position
		float newCamPosition = (xFirst + xLast) / 2 * Constants.BOX_TO_WORLD;
		float camPosition = gameCamera.position.x;
		if(newCamPosition < camPosition && camPosition - MOVING_SMOOTH * elapsedTime > newCamPosition) {
			newCamPosition = camPosition - MOVING_SMOOTH * elapsedTime;
		} else if(newCamPosition > camPosition && camPosition + MOVING_SMOOTH * elapsedTime < newCamPosition) {
			newCamPosition = camPosition + MOVING_SMOOTH * elapsedTime;
		}
		backgroundPosition = (int) ((backgroundPosition + (newCamPosition - camPosition) / 2) % background.getWidth());
		gameCamera.position.set(newCamPosition, Constants.HEIGHT_RESOLUTION/2 * gameCamera.zoom, gameCamera.position.z);
		gameCamera.update();
		interfaceCamera.update();

		for (IUpdatable updatable : updatables) {
			updatable.update(elapsedTime);
			
			if(updatable instanceof Player) {
				Player p = (Player) updatable;
				if(! p.isDead() && p.getHealth() <= 0) {
					p.kill();
					deadPlayers.add(p);
					sortedPlayers.removeValue(p, true);
					
					if(deadPlayers.size >= players.size - 1) {
						gameover();
					}
				} else if(p.getPosition().y < 0) {
					p.setHealth(p.getHealth() - 10);
					p.setPosition(p.getPosition().x - (100 * Constants.WORLD_TO_BOX), Constants.HEIGHT_RESOLUTION * Constants.WORLD_TO_BOX);
				}
			}
		}
		
		if(sortedPlayers.size > 1) {
			sortedPlayers.sort(comparatorPlayerByDistance);
			for(int i = 0; i < sortedPlayers.size; i++) {
				float regenValue = (float) (BONUS_REGEN - (((BONUS_REGEN * 1.5) / (sortedPlayers.size-1)) * i));
				float energyRegen = 2 - regenValue;
				sortedPlayers.get(i).setHealthRegen(regenValue);
				sortedPlayers.get(i).setEnergyRegen(energyRegen);
			}
		}
	}

	@Override
	public void render() {
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

		//Render game content
		gameBatch.setProjectionMatrix(gameCamera.combined);
		gameBatch.begin();
		
		//Render background
		float paintingPositionY = 0;
		while(paintingPositionY < Constants.HEIGHT_RESOLUTION * gameCamera.zoom) {
			float paintingPositionX = backgroundPosition - Constants.WIDTH_RESOLUTION;
			while(paintingPositionX < gameCamera.position.x + (Constants.WIDTH_RESOLUTION / 2 * gameCamera.zoom)) {
				if(paintingPositionX > gameCamera.position.x - (Constants.WIDTH_RESOLUTION / 2 * gameCamera.zoom) - background.getWidth()) {
					gameBatch.draw(background, paintingPositionX, paintingPositionY);
				}
				paintingPositionX += background.getWidth();
			}
			paintingPositionY += background.getHeight();
		}
		
		for(int i = 0; i < players.size; i++) {
			Player p = players.get(i);
			p.render(gameBatch);
		}
		for (Tile tile : mapGenerator.getTiles()) {
			tile.render(gameBatch);
		}
		for (IRenderable renderable : renderables) {
			renderable.render(gameBatch);
		}
		gameBatch.end();
		
		//Render interface
		interfaceBatch.setProjectionMatrix(interfaceCamera.combined);
		interfaceBatch.begin();
		for(int i = 0; i < players.size; i++) {
			Player p = players.get(i);
			float x = (p.getPosition().x * Constants.BOX_TO_WORLD - gameCamera.position.x) / gameCamera.zoom + Constants.WIDTH_RESOLUTION/2 - 50;
			defaultFont.draw(interfaceBatch, p.getName()+" - HP:"+(int) p.getHealth(), x, Constants.HEIGHT_RESOLUTION - 3 * 20);
			defaultFont.draw(interfaceBatch, "Energy:"+(int) p.getEnergy(), x, Constants.HEIGHT_RESOLUTION - 4 * 20);
		}
		if(Constants.IS_DEBUG_MODE) {
			defaultFont.draw(interfaceBatch, "FPS: "+(int)(1/Gdx.graphics.getDeltaTime()), 20, 20);
			defaultFont.draw(interfaceBatch, "Bodies: "+world.getBodyCount(), 20, 40);
			defaultFont.draw(interfaceBatch, "Debug msg: "+debugMsg, 20, 60);
		}
		defaultFont.draw(interfaceBatch, "Level: "+getCurrentLevel(), Constants.WIDTH_RESOLUTION - 70, Constants.HEIGHT_RESOLUTION - 20);
		interfaceBatch.end();
		
		if(Constants.IS_DEBUG_MODE) {
			debugRenderer.render(world, gameCamera.combined.cpy().scl(Constants.BOX_TO_WORLD));
		}
	}

	@Override
	public void dispose() {
		gameBatch.dispose();
		interfaceBatch.dispose();
		Player.dispose();
		BigLeftMonster.dispose();
		background.dispose();
	}
	
	private void gameover() {
		for(Player p : players) {
			if(!p.isDead()){
				deadPlayers.add(p);
			}
		}
		runStickmanRun.changeCurrentScreen(RunStickmanRun.INDEX_MULTIPLAYER_RANK_SCREEN, true);
	}
	
	public Array<Player> getInvertedRanking() {
		return deadPlayers;
	}
	
	@Override
	public void beginContact(Contact contact) {
		Body bodyA = contact.getFixtureA().getBody(), bodyB = contact.getFixtureB().getBody();
		Player player = null;
		
		if(bodyA.getUserData() instanceof Player) {
			player = (Player) bodyA.getUserData();
			if((bodyB.getUserData() instanceof AbstractEntity || bodyB.getUserData() instanceof SolidBoxTile) && bodyA.getLinearVelocity().y < 0f) {
				player.setJumping(false);
				player.setDoubleJumping(false);
			}
		}
		
		if(bodyB.getUserData() instanceof Player) {
			player = (Player) bodyB.getUserData();
			if((bodyA.getUserData() instanceof AbstractEntity || bodyA.getUserData() instanceof SolidBoxTile) && bodyB.getLinearVelocity().y < 0f) {
				player.setJumping(false);
				player.setDoubleJumping(false);
			}
		}
		
		if(player != null) {
			if(bodyA.getUserData() instanceof IDamagingTile) {
				player.setHealth(player.getHealth() - ((IDamagingTile) bodyA.getUserData()).getDamage());
			} else if(bodyB.getUserData() instanceof IDamagingTile) {
				player.setHealth(player.getHealth() - ((IDamagingTile) bodyB.getUserData()).getDamage());
			}
		}
	}
	
	@Override
	public Player getFirstPlayer() {
		Player player = null;
		
		for(Player p : players) {
			if(player == null || player.isDead() || (p.getPosition().x > player.getPosition().x && ! p.isDead())) {
				player = p;
			}
		}
		
		return player;
	}
	
	public Player getLastPlayer() {
		Player player = null;
		
		for(Player p : players) {
			if(player == null || player.isDead() || (p.getPosition().x < player.getPosition().x && ! p.isDead())) {
				player = p;
			}
		}
		
		return player;
	}
	
	@Override
	public World getWorld() {
		return world;
	}

	@Override
	public boolean keyDown(int keycode) {
		int indexPlayer;
		
		if(keycode == keyMap.keyCodeJump 
		|| keycode == keyMap.keyCodeLeft 
		|| keycode == keyMap.keyCodeRight 
		|| keycode == keyMap.keyCodeSprint) {
			indexPlayer = 0;
		} else {
			indexPlayer = 1;
		}
		handleKeyDown(keycode, indexPlayer);
		return true;
	}

	@Override
	public boolean keyUp(int keycode) {
		int indexPlayer;
		
		if(keycode == keyMap.keyCodeJump 
		|| keycode == keyMap.keyCodeLeft 
		|| keycode == keyMap.keyCodeRight
		|| keycode == keyMap.keyCodeSprint) {
			indexPlayer = 0;
		} else {
			indexPlayer = 1;
		}
		handleKeyUp(keycode, indexPlayer);
		return true;
	}

	@Override
	public boolean axisMoved(Controller arg0, int arg1, float arg2) {
		if(arg1 == Ouya.AXIS_LEFT_X) {
			if(arg2 > 0.4f) {
				handleKeyDown(keyMap.keyCodeRight, Controllers.getControllers().indexOf(arg0, true));
				handleKeyUp(keyMap.keyCodeLeft, Controllers.getControllers().indexOf(arg0, true));
			} else if(arg2 < -0.4f) {
				handleKeyDown(keyMap.keyCodeLeft, Controllers.getControllers().indexOf(arg0, true));
				handleKeyUp(keyMap.keyCodeRight, Controllers.getControllers().indexOf(arg0, true));
			} else {
				handleKeyUp(keyMap.keyCodeLeft, Controllers.getControllers().indexOf(arg0, true));
				handleKeyUp(keyMap.keyCodeRight, Controllers.getControllers().indexOf(arg0, true));
			}
		}
		return true;
	}

	@Override
	public boolean buttonDown(Controller arg0, int arg1) {
		handleKeyDown(arg1, Controllers.getControllers().indexOf(arg0, true));
		return true;
	}

	@Override
	public boolean buttonUp(Controller arg0, int arg1) {
		handleKeyUp(arg1, Controllers.getControllers().indexOf(arg0, true));
		return true;
	}
	
	private void handleKeyDown(int keyCode, int indexPlayer) {
		if(keyCode == keyMap.keyCodeLeft || keyCode == keyMap.keyCodeLeftBis) {
			players.get(indexPlayer).moveLeft(true);
		} else if(keyCode == keyMap.keyCodeRight || keyCode == keyMap.keyCodeRightBis) {
			players.get(indexPlayer).moveRight(true);
		} else if(keyCode == keyMap.keyCodeJump || keyCode == keyMap.keyCodeJumpBis) {
			players.get(indexPlayer).jump(true);
		} else if(keyCode == keyMap.keyCodeSprint || keyCode == keyMap.keyCodeSprintBis) {
			players.get(indexPlayer).sprint(true);
		}
	}
	
	private void handleKeyUp(int keyCode, int indexPlayer) {
		if(keyCode == keyMap.keyCodeLeft || keyCode == keyMap.keyCodeLeftBis) {
			players.get(indexPlayer).moveLeft(false);
		} else if(keyCode == keyMap.keyCodeRight || keyCode == keyMap.keyCodeRightBis) {
			players.get(indexPlayer).moveRight(false);
		} else if(keyCode == keyMap.keyCodeJump || keyCode == keyMap.keyCodeJumpBis) {
			players.get(indexPlayer).jump(false);
		} else if(keyCode == keyMap.keyCodeSprint || keyCode == keyMap.keyCodeSprintBis) {
			players.get(indexPlayer).sprint(false);
		}
	}
}
